#include <linux/socket.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/proc_fs.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include "network.h"
#include "network.c"
#include "hooks.h"
#include "rules.h"
#include "rules.c"
#include "log.h"
#include "fw5.h"
#include "conn_tab.h"
#include "server_protect.h"
#include "server_protect.c"
// Hook LOCAL_IN
struct nf_hook_ops hook_localin_ops = {
	.list.next = NULL,
	.list.prev = NULL,
	.hook = packetHandler,	
	.pf = PF_INET, /* IPv4 */
	.hooknum = NF_INET_LOCAL_IN,
	.priority = NF_IP_PRI_NAT_DST 
}; 
// Hook FORWARD
struct nf_hook_ops hook_forward_ops = {
	.list.next = NULL,
	.list.prev = NULL,
	.hook = packetHandler,	
	.pf = PF_INET, /* IPv4 */
	.hooknum = NF_INET_FORWARD,
	.priority = NF_IP_PRI_MANGLE  
}; 
// Hook LOCAL_OUT
struct nf_hook_ops hook_localout_ops = {
	.list.next = NULL,
	.list.prev = NULL,
	.hook = packetHandler,	
	.pf = PF_INET, /* IPv4 */
	.hooknum = NF_INET_LOCAL_OUT,
	.priority = NF_IP_PRI_NAT_SRC
	//.hooknum = NF_INET_POST_ROUTING,
	//.priority = NF_IP_PRI_FIRST,
}; 


// Main hook callback function
static unsigned int packetHandler(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *)){
	rawPacket_t packet = { 0 };				// packet to extract
	char ruleCheck = 0;						// verdict of rules
	int matchedRuleIndex = 0;					// index of rule matched
	int matchedStringIndex = 0;					// index of string matched offset in packet data
	connection_t *conv = NULL;				// conversation if found in tcp mode
	tcpHeaderParams_t tcpParams = { 0 };	// tcp header params
	tcp_commands_t tcpCommand;				// tcp command
	tcp_conn_t returnedState;				// the previous state in tcp mode if exists
	unsigned short protocolBits = 0;		// convert between PROT to CONFIG protocol
	if(skb == NULL){
		return NF_ACCEPT;
	}	
	
	//Get information from sk_buff
	makeRawPacket(skb,&packet,&tcpParams);
	//printk(" HOOK NUM %d\n",hooknum);
	
	// check if firewall is enabled
	if(!(fw_config & FW_CONFIG_ACTIVE)){		
		// Log the REASON_FW_INACTIVE for this packet.
		writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_FW_INACTIVE, hooknum,STRING_MATCH_DEFAULT);
		return NF_ACCEPT;
	}	
	if(packet.version != IP_VERSION){		
		// Log the REASON_NOT_IPV4 for this packet.
		writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_NOT_IPV4, hooknum,STRING_MATCH_DEFAULT);
		return NF_DROP;
	}
	
	// get packet protocol in config bits
	if(packet.protocol == PROT_ICMP){
		protocolBits = FW_CONFIG_ICMP;
	}else if(packet.protocol == PROT_TCP){
		protocolBits = FW_CONFIG_TCP;
	}else if(packet.protocol == PROT_UDP){
		protocolBits = FW_CONFIG_UDP;
	}
	// protocol is off
	if(!(fw_config & protocolBits)){		
		// Log the REASON_PROT_NOT_ENFORCED for this packet.
		writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_PROT_NOT_ENFORCED, hooknum,STRING_MATCH_DEFAULT);
		return NF_ACCEPT;
	}
	// packet is ready for inspection
	ruleCheck = checkPermission(&packet,1,&matchedRuleIndex, &matchedStringIndex);	
	/*
	if(ruleCheck == svn addNF_ACCEPT) ruleCheck = 1;
	if(ruleCheck == NF_DROP) ruleCheck = 0;  
	if(ruleCheck == UNMATCHED_PACKET) ruleCheck = -1;	
	*/
	if(ruleCheck == UNMATCHED_PACKET){		
		if(fw_config & FW_CONFIG_CLEANUP_ACCEPT){
			if(fw_config & FW_CONFIG_REMOTE_FILE_PROTECT){ // protect remote file
				if(remoteFileProtection(&packet) == NF_DROP){
					writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_REMOTE_FILE_ATTACK, hooknum,STRING_MATCH_DEFAULT);
					return NF_DROP;
				}
			}
			writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_ACCEPT, REASON_NO_MATCHING_RULE, hooknum,STRING_MATCH_DEFAULT);
			return NF_ACCEPT;
		}else{
			writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_NO_MATCHING_RULE, hooknum,STRING_MATCH_DEFAULT);
			return NF_DROP;			
		}
	}		
	// Allow localhost comminucation
	if(packet.source_ip == ntohl(LOCALHOST) || packet.dest_ip == ntohl(LOCALHOST)){
		return ruleCheck;	
	}
	if(ruleCheck == NF_ACCEPT){
		if(packet.protocol == PROT_TCP){
			//TCP
			if(underAttack(&packet,(char *)&tcpParams) == ATTACK_XMAS){			
				writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_XMAS_PACKET, hooknum,STRING_MATCH_DEFAULT);
				return NF_DROP;
			}		
			//Connection Table
			if(fw_config & FW_CONFIG_TCP && fw_config & FW_CONFIG_CONN_TRACK){
				 //Find previous conversation			
				 conv = findConversation(&packet);
				 if(conv == CONV_NOT_FOUND){				
					 tcpParams.side = CLIENT;
				 }else{				
					 if(conv->cli_ip == packet.source_ip){
						tcpParams.side = CLIENT;
					 }else{
						 tcpParams.side = SERVER;
					 }
				 }
				 tcpCommand = getCommand(tcpParams);
				 if(tcpCommand == BAD_COMMAND){
					writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_OUT_OF_STATE, hooknum,STRING_MATCH_DEFAULT);				
					return NF_DROP;
				 }
				 //State
				 if(conv == CONV_NOT_FOUND){
					 //Must be for the pre state
					 if(tcpParams.syn && !tcpParams.ack){
						 returnedState = TCP_CONN_SYN_SENT;
					 }else{
						 returnedState = BAD_STATE;					
					 }
				 }else{
					returnedState = nextState(conv->state,tcpCommand,tcpParams.side);
				 }
				 if(returnedState == BAD_STATE){
					writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_OUT_OF_STATE, hooknum,STRING_MATCH_DEFAULT);
					return NF_DROP;
				 }
				 
				 if(conv == CONV_NOT_FOUND){				
					 //Add sets the first state
					 conv = addConversation(&packet);	
					 if(conv == NULL){
						 writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_CONNECTION_TABLE_FULL, hooknum,STRING_MATCH_DEFAULT);
						 return NF_DROP;
					 }
					 
				 }else{			 
					 //Set new state				
					 setState(conv,returnedState,tcpCommand);
					 //printConversationTable();
				}
			}
		}
		if(fw_config & FW_CONFIG_REMOTE_FILE_PROTECT){ // protect remote file
			printk("YES\n");
			if(remoteFileProtection(&packet) == NF_DROP){
				writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, NF_DROP, REASON_REMOTE_FILE_ATTACK, hooknum,STRING_MATCH_DEFAULT);
				return NF_DROP;
			}
		}else{
			printk("NO\n");
		}
	}
	writeLog(packet.source_ip, packet.dest_ip, packet.source_port, packet.dest_port, packet.protocol, ruleCheck, matchedRuleIndex, hooknum,matchedStringIndex);
	return ruleCheck;
}
